/*
 * Project CHEN Rendering Engine : /light.hpp
 * Created: 2020-12-24, Last modified:  2021-01-27
 * 
 * This header file includes the basic implementations of several light
 * sources.
 * 
 * Copyright (C) 2021 Hineven (hineven@pku.edu.cn)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _H_LIGHT
#define _H_LIGHT
#include "heads.hpp"
#include "spectrum.hpp"
#include "strc.hpp"
namespace chen {
class Light {
public:
	int flags;
	Spectrum identity;
	Float identity_energy;
	mutable const Structure * strc;
	Light (int t, const Spectrum & iden): flags(t), identity(iden) {
		identity_energy = identity.energy();
		strc = nullptr;
	}
	inline virtual Spectrum LeFrom (const Ray & r) const {
		LOG(FATAL) << "[Light] Not implemented function 'LeFrom()' called, flags: " << flags;
		return 0;
	}
	// Calculate the RADIANCE this light illuminates one interaction.
	// Unavaliable when the light is an area light.
	inline virtual Spectrum Li (const Interact & si, Vector3 * wi, bool * occ) const {
		LOG(FATAL) << "[Light] Not implemented function 'Li()' called, flags: " << flags;
		return 0;
	}
	// Get the RADIANCE at an surface interaction of an outgoing ray
	virtual Spectrum Lo (const Interact & s, const Vector3 & wo) const = 0;
	virtual Spectrum sampleLo (const Point2 & up, const Point2 & ud, Ray * r, Float * pdf, Float * cosv) const {
		LOG(FATAL) << "[Light] Not implemented function 'sampleLo()' called, fkags: " << flags;
		return 0;
	}
	// The (possibly imaginary) surface area of the light source.
	// Used for sampling photons.
	virtual Float area () const = 0;
	// Total power of the area light, used to estimate direct illumination.
	virtual Float power () const = 0;
	inline virtual ~Light () {}
};
// The most basic light class. It only provides a simple background radiation
// without any deliverance of energy into the scene.
class BasicLight: public Light {
public:
	inline BasicLight (const Spectrum & identity):
		Light::Light(FLIGHT_LI, identity) {}
	inline Spectrum Li (const Interact & si, Vector3 * wi, bool * occ) const {
		unused(si);
		unused(wi);
		unused(occ);
		return 0;
	}
	inline Spectrum Lo (const Interact & si, const Vector3 & wo) const {
		unused(si);
		unused(wo);
		return 0;
	}
	// no need to implement sampleLo
	inline Spectrum LeFrom (const Ray & r) const {
		unused(r);
		return identity;
	}
	inline Float area () const {
		return 0;
	}
	inline Float power () const {
		return 0;
	}
};
// Diffused point light. Radiation is uniformly distributed in all directions.
class DiffusePointLight: public Light {
public:
	Point3 w_p;
	inline DiffusePointLight (const Spectrum & identity, const Point3 & w_p):
		Light::Light(FLIGHT_SAMPLE_LO|FLIGHT_LI, identity), w_p(w_p) {}
	Spectrum LeFrom (const Ray & r) const {
		unused(r);
		return 0;
	}
	Spectrum Li (const Interact & si, Vector3 * wi, bool * occ) const ;
	inline Spectrum Lo (const Interact & si, const Vector3 & wo) const {
		unused(si);
		unused(wo);
		return 0;
	}
	inline Spectrum sampleLo (const Point2 & up, const Point2 & ud, Ray * r, Float * pdf, Float * cosv) const {
		r->o = w_p;
		r->t = Infinity;
		Float sintheta = std::sqrt(ud.y);
		Float phi = ud.x*(Float)(2*M_PI);
		r->d.x = sin(phi)*sintheta;
		r->d.y = cos(phi)*sintheta;
		Float z = std::sqrt(max((Float)0, (Float)1-sintheta*sintheta));
		r->d.z = up.x < 0.5 ? -z : z;
		*pdf = 1.0/M_PI_4;
		*cosv = 1; // no surface normal, 1 by default
		return identity*(Float)(1.0/M_PI_4);
	}
	// Assume the point light is a unit sphere
	inline Float area () const {return (Float)(M_PI*4);}
	inline Float power () const {return (Float)(M_PI*4)*identity_energy;}
};

class AreaLight: public Light {
public:
    const Shape * shape;
	bool flipped;
    inline AreaLight (const Spectrum & iden, const Shape * shape, bool flipped):
		Light::Light(FLIGHT_AREA|FLIGHT_SAMPLE_LO|FLIGHT_SAMPLE_LI, iden),
		shape(shape), flipped(flipped) {}
    // Sample the RADIANCE emmited from interaction in of the area light in
	// wo direction.
	inline Spectrum Lo (const Interact & s, const Vector3 & wo) const {
		unused(s);
		unused(wo);
		LOG(FATAL) << "[Light] Unimplemented function called (Lo unimplemented version).";
	}
    virtual Spectrum Lo (
		const Interact & in, const Vector3 & wo,
		const Point2 & uv
	) const = 0; 
	// Random sample a light ray carrying RADIANCE which starts from the area
	// light and ends at interacion s.
	virtual Spectrum sampleLi (
		const Interact & s, const Point2 & u,
		Vector3 * wi, Float * pdf, bool * occ
	) const = 0;
    // Random sample a light ray carrying RADIANCE starts from the area light.
	// RADIANCE can be used to reverse tracing photons by multplying the
	// cosv factor, which is the cosine value of the angle between area light
	// geometry normal and the light ray direction.
	virtual Spectrum sampleLo (
		const Point2 & up, const Point2 & ud,
		Ray * r, Float * pdf, Float * cosv
	) const = 0;
    // Total area of the area light, used to sample photons
    virtual Float area () const = 0;
	virtual Float power () const = 0;
};

}
#endif